﻿///
/// NetworkDeploy.fs
/// Defines the elements to create nodes and connections for a given network.
///

namespace PathFinder

open System
open System.Collections
open System.Collections.Generic

open Network
open NetworkSetup

#light

///
/// Contains the most important elements to deploy a network.
///
module public NetworkDeploy =
    (* Exceptions *)
    ///
    /// Raised when, generally speaking, it was not possible to deploy a network from an adjacency matrix.
    ///
    exception public CouldNotDeployNetworkException of string
    ///
    /// Raised when an operation is being tried to be performed in case of no network deployed.
    ///
    exception public ForbiddenOperationOnUndeployedNetwork of string
    (* Types *)
    ///
    /// Defines the network, a container for nodes.
    ///
    type private NetworkDictionary = Dictionary<NodeIdentifier, NetworkNode>
    ///
    /// Defines the final object incapsulating the querying logic for nodes in the network.
    ///
    type public NetworkDeployment =
        (* List of nodes *)
        val mutable private netlist: NetworkDictionary
        (* Constructor passing an adjacency matrix *)
        new () = {
            netlist = new NetworkDictionary ();
            }
        (* Methods *)
        ///
        /// Resets the netlist.
        ///
        member private self.Reset () =
            self.netlist <- new NetworkDictionary ()
        ///
        /// Initializes the network deployment.
        /// Every call causes the reinitialization of the network.
        ///
        member public self.Deploy (setup_nodes: SetupNode list, setup_conns: SetupConnection list) =
            self.Reset () (* Resetting *)
            let mutable nodes = new Set<NodeIdentifier * NodeCoordinate> ([]) (* Will be filled with the node ids *)
            (* Checking elements *)
            if setup_nodes.Length <= 0 || setup_conns.Length <= 0 then
                raise (CouldNotDeployNetworkException ("At least one list empty!"))
            (* Populating with nodes *)
            for i in setup_nodes do
                nodes <- (nodes.Add (i.Node, i.Coordinates))
            (* Starting populating the netlist, here we insert only nodes without setting neighbourhood *)
            for i in nodes do
                self.netlist.[fst i] <- (new NetworkNode (fst i, snd i))
            (* Setting now neighbourhood with enhanced commands *)
            for i in self.netlist do (* For every node *)
                (* Search for from i to any *)
                let from_i_to_j = List.filter (fun x -> x.NodeFrom = i.Key) setup_conns
                for j in from_i_to_j do
                    let node = self.netlist.[j.NodeTo] (* Finding the node *)
                    i.Value.AddNeighbour (node, j.Cost)
        ///
        /// Initializes the network deployment.
        /// Every call causes the reinitialization of the network.
        /// Adjacency matrix is used.
        ///
        member public self.Deploy (adj: NetworkAdjacencyMatrix) =
            self.Reset () (* Resetting *)
            let keys = adj.Keys (* Getting keys *)
            let mutable nodes = new Set<NodeIdentifier> ([]) (* Will be filled with the node ids *)
            (* Checking adj matrix *)
            if keys.Count = 0 then
                raise (CouldNotDeployNetworkException ("Adjacency matrix empty!"))
            (* Populating with nodes *)
            for i in keys do
                let fromNode = (fst i)
                let toNode = (snd i)
                let weight = adj.[i]
                nodes <- (nodes.Add (fromNode))
                nodes <- (nodes.Add (toNode))
            (* Starting populating the netlist, here we insert only nodes without setting neighbourhood *)
            for i in nodes do
                self.netlist.[i] <- (new NetworkNode (i))
            (* Setting now neighbourhood *)
            for i in self.netlist do (* For every node *)
                for j in self.netlist do (* Again for every node *)
                    let weightToNode = adj.[i.Key, j.Key]
                    if weightToNode > 0.0 then
                        i.Value.AddNeighbour (j.Value, weightToNode)
        ///
        /// Gets the total number of elements in the netlist.
        ///
        member public self.Count =
            self.netlist.Count
        ///
        /// Gets the node.
        ///
        member public self.Item
            with get (id: NodeIdentifier) = 
                try
                    self.netlist.[id] |> Some
                with
                    | :? KeyNotFoundException as ex -> None
        ///
        /// Gets all the node identifiers in the netlist.
        ///
        member public self.AllNodesIdentifiers =
            let mutable nodeids = new Set<NodeIdentifier> ([])
            for i in self.netlist do
                nodeids <- (nodeids.Add (i.Value.Name))
            nodeids

